#ifndef NO_GRAPHIC
#include <SDL2/SDL.h>
#include <SDL2/SDL_thread.h>
#include "graphic.h"
#else
#include <signal.h>
#include <pthread.h>
#endif
#include <stdio.h>
#include <stdbool.h>
#include <strings.h>
#include <libgen.h>
#include <string.h>
#include <stdlib.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif

#include "config.h"
#include "defines.h"
#include "utils.h"
#include "cell.h"
#include "cell_func.h"

#ifdef __EMSCRIPTEN__
#define customBool EM_BOOL
#else
#define customBool bool
#endif

#include "net.h"

#ifndef NO_GRAPHIC
static unsigned      tps = 0;
static time_t        timeBegin = 0, timeNow = 0;
#endif
static unsigned long autoSaveTimer = 0;

#ifndef NO_GRAPHIC
static struct Graphic *g = NULL;
#endif
static struct World *w = NULL;
struct Net *n = NULL;

char filepath[MAX_PATH_LENGTH];

time_t time(time_t *seconds);
customBool mainloop(double time, void* userData);

bool isRunning();
#ifndef NO_GRAPHIC
void buttonDelete(struct Graphic *g, struct World *w);
void buttonWall(struct Graphic *g, struct World *w);
void buttonFood(struct Graphic *g, struct World *w);
void buttonPortal(struct Graphic *g, struct World *w);
void buttonInfo(struct Graphic *g, struct World *w);
#else
static bool running = true;
static void sig_handler(int _) {
    (void)_;
    running = false;
}
#endif

int main(int argc, const char **argv) {
#ifdef NO_GRAPHIC
    signal(SIGINT, sig_handler);
#endif
    static struct World world;
    srand(time(0));

    // Saving file path
    char file[MAX_PATH_LENGTH];
    strncpy(file, argv[1], MAX_PATH_LENGTH);

#ifndef NO_GRAPHIC
    // Grpahic initialisation
    char* dname = dirname((char*)argv[0]);
    char* fontpath = strcat(dname, "/terminus.ttf");
    struct Graphic *graphic = G_init(DEFAULT_WIDTH, DEFAULT_HEIGHT, fontpath);

    SDL_Thread *thread = NULL;
#else
    pthread_t thread = 0;
#endif

    // Help info
    if(argc > 1) {
        if(!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            puts("life <world file> [server ip addres]");
            return 0;
        }
    }

    // Connect to the server
    if(argc == 3) {
        n = N_init((char*)argv[2], 8081);
        if(n != NULL) {
            N_send(n, (struct Package){.type = P_AUTH});
#ifndef NO_GRAPHIC
            thread = SDL_CreateThread(N_readServerMessages, "Msg reader", n);
            if(thread == NULL) {
                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateThread failed: %s\n", SDL_GetError());
                N_close(n);
                return -1;
            }
#else
            if(pthread_create((&thread), NULL, N_readServerMessages, (void*)n)) {
                fprintf(stderr, "Failed to create a thread\n");
                N_close(n);
            }
#endif
        }
    }

#ifdef __EMSCRIPTEN__
    strncpy(file, "webworld.bin", MAX_PATH_LENGTH);
    importWorld(file, &world);
#else
    if(argc > 1) {
        if(strlen(argv[1]) > MAX_PATH_LENGTH) {
            printf("Path too long, recompile with MAX_PATH_LENGTH >= %ld\n", strlen(argv[1]));
            return(1);
        }
        importWorld(file, &world);
    } else {
        printf("Please create world file using `mkworld` utile.\n");
        return -1;
    }
#endif
#ifndef NO_GRAPHIC
    time(&timeBegin);
    registerButton(graphic, "DEL",  (SDL_Color){255, 0,   0},   buttonDelete);
    registerButton(graphic, "WALL", (SDL_Color){255, 255, 255}, buttonWall);
    registerButton(graphic, "FOOD", (SDL_Color){200, 200, 0},   buttonFood);
    registerButton(graphic, "PORT", (SDL_Color){0,   128, 128}, buttonPortal);
    registerButton(graphic, "INFO", (SDL_Color){0,   0,   0},   buttonInfo);
    graphic->renderActive = true;
    graphic->running = true;
    g = graphic;
#endif
    autoSaveTimer = 0;
    w = &world;
#ifdef __EMSCRIPTEN__
    emscripten_request_animation_frame_loop(mainloop, 0);
#else
    while(mainloop(0, NULL));
#endif
    exportWorld(w);
#ifndef NO_GRAPHIC
    G_close(g);
#endif
    if(n != NULL) {
        N_close(n);
#ifndef NO_GRAPHIC
        if(thread != NULL)
            SDL_WaitThread(thread, NULL);
#endif
    }
    return(0);
}

customBool mainloop(double _time, void* userData) {
#ifndef NO_GRAPHIC
    keypress(w, g);
    if(g->renderActive) render(w, g);
    if(g->pause) return(g->running);
    if(g->oneStep) {
        g->oneStep = false;
        g->pause = true;
    }
#endif
    if(autoSaveTimer > 100000) {
        fprintf(stderr, "Autosave...\n");
        exportWorld(w);
        autoSaveTimer = 0;
        fprintf(stderr, "Done!\n");
    }
    autoSaveTimer++;
    if(w->timeDirection) w->time++;
    else w->time--;
    if(w->time > w->dayLength) {
        w->timeDirection = false;
    } else if(w->time < -w->dayLength) {
        w->timeDirection = true;
    }
#ifndef NO_GRAPHIC
    if(g->showTPS) {
        time(&timeNow);
        tps++;
    }
#endif
    for(int y=0; y<w->height; y++) {
        for(int x=0; x<w->width; x++) {
            int i = cordsToIndex(w, x, y);
            switch (w->cells[i].type) {
            case(T_MINWALL):
                if(w->cells[i].energy <= 0) {
                    w->cells[i] = newNullCell();
                    w->cells[i].type = T_NOTHING;
                }
                break;
            case(T_FOOD):
                if(w->cells[i].age >= w->foodMaxAge && w->foodMaxAge > 0) {
                    w->cells[i].type = T_NOTHING;
                }
                if(w->cells[i].energy < 1) w->cells[i].type = T_NOTHING;
                w->cells[i].age++;
                break;
            case(T_CELL):
                if(w->cells[i].energy > w->maxEnergy && w->maxEnergy > 0)
                    w->cells[i].energy = w->maxEnergy;
                w->cells[i].energy--; w->cells[i].age++;
                if(w->cells[i].age >= w->maxAge && w->maxAge > 0) {
                    killCell(&w->cells[i], true);
                    continue;
                }
                processGenome(w, n, i);
                if(w->cells[i].energy < 1)
                    killCell(&w->cells[i], false);
                break;
            case(T_WALL):
            case(T_PORTAL):
            case(T_NOTHING):
                break;
            }
        }
    }
#ifndef NO_GRAPHIC
    if(g->showTPS) {
        if(timeNow-timeBegin >= 1.0) {
            fprintf(stderr, "TPS: %d\n", tps);
            tps = 0;
            time(&timeBegin);
        }
    }
    if(n != NULL) {
        struct Package pkg;
        for(N_popPkg(n, &pkg); n->pkgCount > 0; N_popPkg(n, &pkg)) {
            int x = rand() % w->width,
                y = rand() % w->height;
            if(w->cells[cordsToIndex(w, x, y)].type == T_NOTHING)
                memcpy(&w->cells[cordsToIndex(w, x, y)], &pkg.cell, sizeof(struct Cell));
        }
    }
#endif
    return(isRunning());
}

bool isRunning() {
#ifndef NO_GRAPHIC
    return g->running;
#else
    return running;
#endif
}

#ifndef NO_GRAPHIC
void buttonDelete(struct Graphic *g, struct World *w) {
    g->mouseFunc[g->mouseButton] = DM_DEL;
}
void buttonWall(struct Graphic *g, struct World *w) {
    g->mouseFunc[g->mouseButton] = DM_WALL;
}
void buttonFood(struct Graphic *g, struct World *w) {
    g->mouseFunc[g->mouseButton] = DM_FOOD;
}
void buttonPortal(struct Graphic *g, struct World *w) {
    g->mouseFunc[g->mouseButton] = DM_PORTAL;
}
void buttonInfo(struct Graphic *g, struct World *w) {
    g->mouseFunc[g->mouseButton] = DM_INFO;
}
#endif
